from flask import current_app, jsonify, g, request, session, render_template
from . import news_blu
from info.models import User, News, Category, Comment, CommentLike
from info.utils.response_code import RET
from info import db, constants
from info.utils.commons import login_status


# 3---使用蓝图对象
# 在蓝图对象上调用route装饰器注册路由(只有在应用对象上注册这个蓝图后才是注册了路由)
@news_blu.route('/')
def index():
    # 一.右上角用户信息的展示
    # 1.尝试从redis中获取用户缓存信息
    user_id = session.get('user_id')
    # 2.定义结果初始值
    user = None
    # 2.1.判断获取结果是否存在
    if user_id:
        try:
            # 根据user_id查询mysql
            user = User.query.get(user_id)
        except Exception as e:
            current_app.logger.error(e)
            # 根据实际业务逻辑，用户如未登录，不需要进行返回，因为浏览页面的时候用户登录不是一个充分条件
            # return jsonify(errno=RET.DATAERR, errmsg='数据查询失败')

    # 二.首页点击排行的展示
    """
    1.查询数据
    2.检查数据
    3.使用容器存储数据并传递给模板
    4.使用模板显示数据
    """
    # 1.查询新闻数据，按点击次数进行降序查询 并限制查询条数，自定义6条
    try:
        news = News.query.order_by(News.clicks.desc()).limit(6)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAERR, errmsg='查询数据库失败')
    # 2.检查数据是否存在
    if not news:
        return jsonify(errno=RET.NODATA, errmsg='无新闻数据')
    # 3.因为查询到的数据为对象列表，则定义一个列表容器
    news_list = []
    # 4.在遍历时增加一个检查news是否为空的条件
    # 其实前面已经进行了数据是否存在的判断，此处进行判断有些多此一举。
    for new in news if news else None:
        # 将对象new转换成字典格式存入列表容器中
        news_list.append(new.to_dict())

    # 三.新闻分类展示
    """
    1.查询数据
    2.检查数据
    3.定义容器储存数据
    4.将数据传递给模板
    5.模板显示数据
    """
    # 1.查询数据
    try:
        # 查询所有分类数据
        categories = Category.query.all()
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAERR, errmsg='查询分类数据失败')
    # 2.检查数据是否存在
    if not categories:
        return jsonify(errno=RET.NODATA, errmsg='无分类数据')
    # 3.定义容器，用于遍历查询得到的对象并调用to_dict方法转成字典存储进容器中
    category_list = []
    for category in categories if categories else None:
        category_list.append(category.to_dict())
    # 4.将查询的结果存入data中，并通过模板进行展示
    data = {
        # 将整理好的数据放在字典中，然后从模板中返回字典即可
        # 判断查询结果user，从模板中返回
        'user_info': user.to_dict() if user else None,
        'new_info': news_list,
        'category_list': category_list
    }

    return render_template('news/index.html', data=data)


# 四.新闻列表的展示
# 请求方法为get，不需要指定请求方法，默认get即可
@news_blu.route('/news_list')
def news_list():
    """
    新闻列表数据显示
    1.获取参数：cid/page/per_page，需要与前端请求时的请求参数中的key一致
    2.校验参数，此处有两个参数非必须，所以不用判断参数完整性；因需要的参数为int型，所以需要进行类型转换
    3.查询数据：查询mysql数据库获取数据，根据分类id cid进行查询，需要按新闻发布时间进行倒序排序，且需要进行分页查询
    3.查询数据时
    4.定义存储查询数据的容器，遍历查询到数据并转为字典格式放入容器中
    5.将数据返回,通过jsonify进行返回
    :return:
    """
    # 1.获取参数 page per_page在未获取到时设置默认值为1 10
    cid = request.args.get('cid')
    page = request.args.get('page', 1)
    per_page = request.args.get('per_page', 10)
    # 2.校验参数：有两个参数是非必须的，所以不需要验证完整性；
    # 需要int型的数据格式，此时进行类型的转换
    try:
        cid, page, per_page = int(cid), int(page), int(per_page)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAERR, errmsg='数据类型错误')
    # 3.查询mysql数据库：根据cid进行查询，且需要按照发布时间对查询结果进行排序，同时需要进行分页查询,
    # 定义一个过滤规则列表，用以简化orm查询语句
    filters = []
    if cid > 1:
        filters.append(News.category_id == cid)
    try:
        paginate = News.query.filter(*filters).order_by(News.create_time.desc()).paginate(page,
                                                                                      constants.HOME_PAGE_MAX_NEWS,
                                                                                      False)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAERR, errmsg='查询新闻数据失败')
    # 4.0.获取分页后的数据
    news_list = paginate.items
    total_page = paginate.pages
    current_page = paginate.page
    # 4.1.定义一个用于存储字典类型数据的容器
    news_dict_list = []
    # 4.2.遍历查询结果，并调用to_dict()方法来获取字典格式的数据
    for new in news_list:
        news_dict_list.append(new.to_dict())
    # 4.3.组织需要返回给前端的数据
    data = {
        'news_dict_list': news_dict_list,
        'total_page': total_page,
        'current_page': current_page
    }
    # 5.返回数据给前端
    return jsonify(errno=RET.OK, errmsg='OK', data=data)


# 五.新闻详情页面
@news_blu.route('/<int:news_id>')
@login_status
def detail(news_id):
    """
    新闻详情页面
    1.判断用户此时的登录状态
    2.根据参数cid查询数据库
    3.判断查询结果，如果新闻存在，则新闻点击量加1
    4.返回新闻详情数据给模板
    """
    # 1.判断用户此时的登录状态
    user = g.user
    # 1.2.根据参数cid查询数据库
    try:
        new = News.query.get(news_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAERR, errmsg='新闻获取失败')
    # 1.3.判断查询结果，如果新闻存在，则点击量加1
    if not new:
        return jsonify(errno=RET.NODATA, errmsg='无新闻数据')
    # 新闻存在，点击量加1
    new.clicks += 1
    # 提交更改到数据库中
    try:
        db.session.add(new)
        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        db.session.rollback()
        return jsonify(errno=RET.DATAERR, errmsg='数据保存异常')

    # 新闻评论列表的实现（评论数据/点赞数据)
    # 1.根据news_id获取此新闻下的所有评论
    comment_list = None
    try:
        comment_list = Comment.query.filter(Comment.news_id == news_id).order_by(Comment.create_time.desc()).all()
    except Exception as e:
        current_app.logger.error(e)
        # 不用返回。实际业务逻辑中新闻肯定会存在没有评论的情况。
        # return jsonify(errno=RET.DATAERR, errmsg='评论数据获取失败')

    # 评论点赞的数据显示
    comment_like_ids = []
    if user:
        try:
            # 如果当前用户已登录
            comment_ids = [comment.id for comment in comment_list]
            if len(comment_ids) > 0:
                # 取到当前用户在当前新闻的所有评论点赞的记录
                comment_likes = CommentLike.query.filter(CommentLike.comment_id.in_(comment_ids),
                                                         CommentLike.user_id == user.id).all()

                # 取出记录中所有的评论id
                comment_like_ids = [comment_like.comment_id for comment_like in comment_likes]
        except Exception as e:
            current_app.logger.error(e)

    # 2.定义一个容器存储评论数据的字典格式数据
    comment_dict_list = []
    # 3.遍历查询到的评论数据对象列表
    for comment in comment_list:
        comment_dict =  comment.to_dict()
        comment_dict['islike'] = False
        # 判断用户是否点赞该评论
        if user and comment.id in comment_like_ids:
            comment_dict['islike'] = True

        comment_dict_list.append(comment_dict)

    is_collected = False
    # 判断用户是否收藏过该新闻
    if user:
        if new in user.collection_news:
            is_collected = True

    # 1.4.构造要返回给模板的数据
    data = {
        'user_info': user.to_dict() if user else None,
        'new': new.to_dict(),
        'is_collected': is_collected,
        'comments': comment_dict_list
    }
    # 1.5.返回给模板
    return render_template('news/detail.html', data=data)


# 六.新闻收藏功能
@news_blu.route('/news_collect', methods=['POST'])
@login_status
def news_collect():
    """
    新闻收藏功能
    1.获取用户登录信息
    2.获取参数：new_id/action:collect/cancel_collect
    3.校验参数的完整：all
    4.检查action参数的范围
    5.new_id转成int类型，然后通过new_id查询数据库
    6.检查查询结果
    7.判断用户的选择：收藏或没有收藏，先判断用户有没有收藏过
    8.提交数据到数据库
    9.返回结果
    :return:
    """
    # 1.尝试获取用户登录信息
    user = g.user
    if not user:
        # 用户未登录，则直接返回；因为只有登录了才能进行收藏操作
        return jsonify(errno=RET.SESSIONERR, errmsg='用户未登录')
    # 用户已登录
    # 2.获取参数
    news_id = request.json.get('news_id')
    action = request.json.get('action')
    # 3.检查参数的完整性
    if not all([news_id, action]):
        return jsonify(errno=RET.PARAMERR, errmsg='参数缺失')
    # 参数检查完整后进行以下
    # 4.检查action参数的范围
    if action not in ['collect', 'cancel_collect']:
        return jsonify(errno=RET.PARAMERR, errmsg='参数错误')
    # 5.将new_id转成int整型，因为数据库中使用的id为int型
    try:
        new_id = int(news_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg='参数格式错误')
    # 6.通过new_id查询数据库
    try:
        new = News.query.get(new_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAERR, errmsg='新闻查询失败')
    # 7.检查查询结果
    if not new:
        return jsonify(errno=RET.NODATA, errmsg='无新闻数据')
    # 8.判断用户是否收藏过
    if action == 'collect':
        # 判断用户没有收藏过
        if new not in user.collection_news:
            user.collection_news.append(new)
    else:
        user.collection_news.remove(new)
    # 9.提交数据库更改到数据库
    try:

        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        db.session.rollback()
        return jsonify(errno=RET.DATAERR, errmsg='保存数据失败')
    # 10.返回结果给前端
    return jsonify(errno=RET.OK, errmsg='OK')


# 新闻评论部分
@news_blu.route('/news_comment', methods=['POST'])
@login_status
def news_comment():
    # 1.尝试获取用户
    user = g.user
    if not user:
        return jsonify(errno=RET.SESSIONERR, errmsg='用户未登录')

    # 2.获取并校验参数
    news_id = request.json.get('news_id')
    content = request.json.get('comment')
    parent_id = request.json.get('parent_id')
    # 3.校验参数完整性
    if not all([news_id, content]):
        return jsonify(errno=RET.PARAMERR, errmsg='参数缺失')

    # 4.将news_id/parent_id转成int类型数据
    try:
        news_id = int(news_id)
        if parent_id:
            parent_id = int(parent_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAERR, errmsg='数据格式错误')

    # 5.根据news_id查询mysql中的新闻数据并判断新闻数据是否存在
    try:
        news = News.query.get(news_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DATAERR, errmsg='新闻数据获取失败')
    if not news:
        return jsonify(errno=RET.NODATA, errmsg='新闻不存在')

    # 6.准备模型类实例来保存并提交数据到数据库
    comment = Comment()
    comment.news_id = news_id
    comment.content = content
    comment.user_id = user.id
    if parent_id:
        comment.parent_id = parent_id
    try:
        db.session.add(comment)
        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        db.session.rollback()
        return jsonify(errno=RET.DATAERR, errmsg='数据存储异常')

    # 7.准备返回给前端的数据
    data = comment.to_dict()

    # 8.返回结果给前端
    return jsonify(errno=RET.OK, errmsg='评论成功', data=data)


# 新闻评论点赞功能
@news_blu.route('/comment_like', methods=['POST'])
@login_status
def comment_like():
    # 1.尝试判断用户是否登录
    user = g.user
    if not user:
        return jsonify(errno=RET.SESSIONERR, errmsg='用户未登录')
    # 2.获取参数
    comment_id = request.json.get('comment_id')
    news_id = request.json.get('news_id')
    action = request.json.get('action')
    # 3.判断参数完整性
    if not all([comment_id, news_id, action]):
        return jsonify(errno=RET.PARAMERR, errmsg='参数缺失')
    # 4.因comment_id/news_id为整型数据，对获取的参数进行类型转换
    try:
        comment_id = int(comment_id)
        news_id = int(news_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg='参数格式错误')
    # 5.判断action参数
    if action not in ('add', 'remove'):
        return jsonify(errno=RET.PARAMERR, errmsg='参数不正确')
    # 6.查询评论数据
    try:
        comment = Comment.query.get(comment_id)
    except Exception as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg='获取评论数据失败')
    if not comment:
        return jsonify(errno=RET.NODATA, errmsg='评论不存在')
    # 7.判断点赞动作
    if action == 'add':
        # 点赞
        comment_like = CommentLike.query.filter_by(comment_id=comment_id, user_id=g.user.id).first()
        if not comment_like:
            comment_like = CommentLike()
            comment_like.comment_id = comment_id
            comment_like.user_id = g.user.id
            db.session.add(comment_like)
            # 点赞数目加1
            comment.like_count += 1
    else:
        # 删除点赞数据
        comment_like = CommentLike.query.filter_by(comment_id=comment_id, user_id=g.user.id).first()
        if comment_like:
            db.session.delete(comment_like)
            # 点赞数目减1
            comment.like_count -= 1

    try:
        db.session.commit()
    except Exception as e:
        current_app.logger.error(e)
        db.session.rollback()
        return jsonify(errno=RET.DBERR, errmsg='操作失败')
    # 8.返回结果给前端
    return jsonify(errno=RET.OK, errmsg='操作成功')


@news_blu.route('/favicon.ico')
def favicon():
    return current_app.send_static_file('news/favicon.ico')
